#include <sstream>
#include <stdio.h>
#include <sys/time.h>

#include "config.h"
#include "log.h"
#include "util.h"
#include "../net/eventloop.h"

namespace rocket {

static Logger *g_logger = nullptr;

LogLevel Logger::getLogLevel() { return m_set_level; }

Logger *Logger::GetGlobalLogger() { return g_logger; }

void Logger::InitGlobalLogger() {
    LogLevel global_log_level =
        StringToLogLevel(Config::GetGlobalConfig()->m_log_level);
    printf("Init log level [%s]\n", LogLevelToString(global_log_level).c_str());
    g_logger = new Logger(global_log_level);
    g_logger->init();
}

Logger::Logger(LogLevel level) : m_set_level(level) {
}

void Logger::init() {
    m_async_logger = std::make_shared<AsyncLogger>(
        Config::GetGlobalConfig()->m_log_file_name,
        Config::GetGlobalConfig()->m_log_file_path,
        Config::GetGlobalConfig()->m_log_max_file_size
    );

    m_timer_event = std::make_shared<TimerEvent>(Config::GetGlobalConfig()->m_log_sync_interval, true, std::bind(&Logger::syncLoop, this));
    EventLoop::GetCurrentEventLoop()->addTimeEvent(m_timer_event);  
}

void Logger::syncLoop() {
    std::vector<std::string> tmp_vec;
    {
        ScopeMutex<Mutex> lock(m_mutex);
        m_buffer.swap(tmp_vec);
    }
    m_async_logger->pushLogBuffer(m_buffer);
}

std::string LogLevelToString(LogLevel level) {
    switch (level) {
    case Debug:
        return "DEBUG";
    case Info:
        return "INFO";
    case Error:
        return "ERROR";
    default:
        return "UNKNOWN";
    }
}

LogLevel StringToLogLevel(const std::string &log_level) {
    if (log_level == "DEBUG") {
        return Debug;
    } else if (log_level == "INFO") {
        return Info;
    } else if (log_level == "ERROR") {
        return Error;
    }
    return Unknown;
}

std::string LogEvent::toString() {

    // load time
    struct timeval now_time;

    gettimeofday(&now_time, nullptr);

    struct tm now_time_t;
    localtime_r(&(now_time.tv_sec), &now_time_t);

    char buf[128];
    strftime(&buf[0], 128, "%y-%m-%d %H:%M:%S", &now_time_t);
    std::string time_str(buf);
    int ms = now_time.tv_usec / 1000;
    time_str = time_str + "." + std::to_string(ms);

    m_pid = getPid();
    m_thread_id = getThreadId();

    std::stringstream ss;

    ss << "[" << LogLevelToString(m_level) << "]\t"
       << "[" << time_str << "]\t"
       << "[" << m_pid << ":" << m_thread_id << "]\t";

    return ss.str();
}

void Logger::pushLog(const std::string &msg) {
    ScopeMutex<Mutex> lock(m_mutex); 
    m_buffer.push_back(msg + '\n');
}

void Logger::log() {
    // ScopeMutex<Mutex> lock(m_mutex);
    // std::queue<std::string> tmp;
    // m_buffer.swap(tmp);
    // lock.unlock();

    // while (!tmp.empty()) {
    //     std::string msg = tmp.front();
    //     tmp.pop();

    //     printf(msg.c_str());
    // }
}

AsyncLogger::AsyncLogger(std::string& file_name, std::string &file_path, int max_size) 
    : m_file_name(file_name), m_file_path(file_path), m_max_file_size(max_size) {
    pthread_cond_init(&m_condition, NULL);
    sem_init(&m_semaphore, 0, 0);

    pthread_create(&m_thread, NULL, &AsyncLogger::Loop, this);

    sem_wait(&m_semaphore);
}

void* AsyncLogger::Loop(void* arg) {
    // dump buffer logs
    AsyncLogger* logger = reinterpret_cast<AsyncLogger*>(arg);

    sem_post(&logger->m_semaphore);

    while (!logger->m_stop_flag) {
        ScopeMutex<Mutex> lock(logger->m_mutex);
        while (logger->m_buffer.empty()) {
            pthread_cond_wait(&(logger->m_condition), logger->m_mutex.getMutex());
        }

        std::vector<std::string> tmp{};
        tmp.swap(logger->m_buffer.front());
        logger->m_buffer.pop();

        timeval now;
        gettimeofday(&now, NULL);

        struct tm now_time;
        localtime_r(&(now.tv_sec), &now_time);

        const char* format = "%y%m%d";
        char date[32];
        strftime(date, sizeof(date), format, &now_time);
        
        if (logger->m_file_handler == nullptr) {
            logger->m_reopen_flag = true;
        }
        if (std::string(date) != logger->m_date) {
            logger->m_no = 0;
            logger->m_date = std::string(date);
            logger->m_reopen_flag = true;
        }
        std::stringstream ss;
        ss << logger->m_file_path << logger->m_file_name << "_"
            << std::string(date) << ".";
        std::string log_file_name = ss.str() + std::to_string(logger->m_no) + ".log";

        if (logger->m_reopen_flag) {
            if (logger->m_file_handler) {
                fclose(logger->m_file_handler);
            }
            logger->m_file_handler = fopen(log_file_name.c_str(), "a");
            logger->m_reopen_flag = false;
        }

        while (ftell(logger->m_file_handler) > logger->m_max_file_size) {
            fclose(logger->m_file_handler);
            logger->m_no++;
            log_file_name = ss.str() + std::to_string(logger->m_no) + ".log";
            logger->m_file_handler = fopen(log_file_name.c_str(), "a");
        }

        for (auto &i : tmp) {
            if (!i.empty()) {
                fwrite(i.c_str(), 1, i.length(), logger->m_file_handler);
            }
        }
        fflush(logger->m_file_handler);

    }
    logger->flush();

    return NULL;
}

void AsyncLogger::stop() {
    m_stop_flag = true;
}

void AsyncLogger::flush() {
    if (m_file_handler) {
        fflush(m_file_handler);
    }
}

void AsyncLogger::pushLogBuffer(std::vector<std::string> &vec) {
    {
        ScopeMutex<Mutex> lock(m_mutex);
        m_buffer.push(vec);
    }
    pthread_cond_signal(&m_condition);
}


} // namespace rocket
